# -*- coding: UTF-8 -*-
# 导入numpy部分参数
from numpy import random, array, zeros, append, sum, concatenate, copy, ndenumerate, isnan, rot90, nan, int, float
import tkinter as tk
import tkinter.font as tkFont
import math
# 枚举四个方向
UP, DOWN, LEFT, RIGHT = range(4)


# 游戏主控
class Game2048(tk.Tk, object):
    def __init__(self):
        super(Game2048, self).__init__()
        self.window_size = 540  # 窗体大小
        self.title('Score: 0')  # 窗体标题
        self.geometry('{0}x{0}+111+111'.format(540))
        self.config(background='#bbada0')  # 窗体背景
        self.color_map = {2: ('#776e65', '#eee4da'), 4: ('#776e65', '#ede0c8'), 8: ('#f9f6f2', '#f2b179'),
                          16: ('#f9f6f2', '#f2b179'), 32: ('#f9f6f2', '#f67c5f'), 64: ('#f9f6f2', '#f65e3b'),
                          128: ('#f9f6f2', '#edcf72'), 256: ('#f9f6f2', '#edcc61'), 512: ('#f9f6f2', '#edc850'),
                          1024: ('#f9f6f2', '#edc53f'), 2048: ('#f9f6f2', '#edc22e'), 'base': '#ccc0b3'}
        # color_map：grid颜色
        self.color_map.update(dict.fromkeys([2 ** x for x in range(12, 18)], ('#f9f6f2', '#3c3a32')))
        # 初始化16格nan，控制数据
        self._grid, self._score = zeros(16) + nan, 0
        # 找其中两格子 填充2
        self._grid[random.choice(16, 2, replace=False)] = random.choice([2] * 9 + [4], 2, replace=False)
        # 并将其转化为4*4标准盘
        self._grid = self._grid.reshape((4, 4))
        self.labels = []
        self._build_maze()
        self.action_space = ['u', 'd', 'l', 'r']
        self.n_actions = len(self.action_space)
        self.n_features = 16

    def _build_maze(self):
        for (i, j), value in ndenumerate(self._grid):
            # 设置窗体框架和字体
            frame = tk.Frame(self, width=self.window_size / 4 - 2, height=self.window_size / 4 - 2)
            font = tkFont.Font(family='Helvetica', weight='bold', size=int(self.window_size / 15))
            frame.pack_propagate(0)
            frame.place(x=j * self.window_size / 4 + 1, y=i * self.window_size / 4 + 1)
            # 设置内容和颜色
            (text, color) = ('', self.color_map['base']) if isnan(value) else (
                '{}'.format(int(value)), self.color_map[value][0])
            label = tk.Label(frame, text=text, font=font, fg=color,
                             bg=self.color_map['base'] if isnan(value) else self.color_map[value][1])
            label.pack(fill=tk.BOTH, expand=True)
            self.labels.append(label)

    # 合并相同方块
    @staticmethod
    def _merge_down(grid):
        merge = concatenate((grid, [zeros(4) + nan])) - concatenate(([zeros(4) + nan], grid))  # find the mergable tiles
        merge[2][merge[3] == 0], merge[1][merge[2] == 0] = nan, nan  # remove redundant 0 by 3 same tiles
        score = sum(grid[merge[:4] == 0])
        grid[merge[:4] == 0], grid[merge[1:] == 0] = grid[merge[:4] == 0] * 2, nan  # fill the merged  with new number
        return score

    # 创建新方块
    def _create_tiles(self):
        avail = isnan(self._grid)
        if avail[avail == True].size > 0:
            new_tiles = append(random.choice([20] * 9 + [40]), zeros(avail[avail == True].size - 1) + nan)
            random.shuffle(new_tiles)
            self._grid[avail] = new_tiles

    # 根据
    #  observation_, reward, done = game.step(action)
    def step(self, action):
        self._grid[self._grid % 10 == 0] /= 10
        merge_v, merge_h, grid_copy = copy(self._grid), copy(rot90(self._grid)), copy(self._grid)
        # 从两个方向上尝试合并方块
        map(Game2048._merge_down, [merge_v, merge_h])
        # 判断游戏是否结束
        # if 结束
        if merge_v[isnan(merge_v)].size is 0 and merge_h[isnan(merge_h)].size is 0:
            grid, new_tiles, score = self.get_grid(), self.get_new_tiles(), int(self.get_score())
            max_tile = int(grid[~isnan(grid)].max())
            [self.labels[i].config(text='' if i < 4 or i > 11 else 'GAMEOVER'[i - 4], bg=self.color_map['base']) for i
             in range(16)]
            self.title('Game Over! Tile acheived: {}, Score: {}'.format(max_tile, score))
            # b = self._grid  # next state
            a = array(self._grid.copy().reshape(16))  # next state
            b = array(zeros(16))
            for i in range(len(a)):
                if a[i] == a[i]:
                    b[i] = a[i]
            reward = -1000  # 游戏结束，扣分-1000
            done = True  # 游戏完成
        else:  # else 可合并
            self._grid = rot90(self._grid, RIGHT - action)
            self._grid = array([concatenate((x[isnan(x)], x[~isnan(x)])) for x in self._grid])  # move tiles
            self._grid = rot90(self._grid, -1)
            step_score = Game2048._merge_down(self._grid)  # merge tiles
            self._score += step_score  # add to score
            self._grid = rot90(self._grid, 1)
            self._grid = array([concatenate((x[isnan(x)], x[~isnan(x)])) for x in self._grid])  # move tiles
            self._grid = rot90(self._grid, action - RIGHT)
            if not ((self._grid == grid_copy) | (isnan(self._grid) & isnan(grid_copy))).all():
                self._create_tiles()
            grid, new_tiles, score = self.get_grid(), self.get_new_tiles(), int(self.get_score())
            max_tile = int(grid[~isnan(grid)].max())
            self.title('Move tiles to get {}! Score: {}'.format(2048 if max_tile < 2048 else max_tile * 2, score))
            for (i, j), value in ndenumerate(grid):
                text = '{}'.format('' if isnan(grid[i][j]) else int(grid[i][j]))
                font_color = self.color_map[32][1] if new_tiles[i][j] else self.color_map['base'] if isnan(value) else \
                    self.color_map[value][0]
                self.labels[4 * i + j].config(text=text, fg=font_color,
                                              bg=self.color_map['base'] if isnan(value) else self.color_map[value][1])
            a = array(self._grid.copy().reshape(16))  # next state
            b = array(zeros(16))
            for i in range(len(a)):
                if a[i] == a[i]:
                    b[i] = a[i]
            reward = step_score  # 游戏结束，扣分加分
            done = False  # 游戏完成
        return b, reward, done

    def reset(self):
        self._grid, self._score = zeros(16) + nan, 0
        # 找其中两格子 填充2
        self._grid[random.choice(16, 2, replace=False)] = random.choice([2] * 9 + [4], 2, replace=False)
        # 并将其转化为4*4标准盘
        self._grid = self._grid.reshape((4, 4))
        self.labels = []
        self._build_maze()
        self.update()
        a=self._grid.copy().reshape(16)
        b = array(zeros(16))
        for i in range(len(a)):
            if a[i] == a[i]:
                b[i] = a[i]
        return b

    def get_grid(self):
        grid = copy(self._grid)
        grid[grid % 10 == 0] /= 10
        return grid

    def get_new_tiles(self):
        grid = zeros((4, 4), int)
        grid[self._grid % 10 == 0] = 1
        return grid

    # 返回分数
    def get_score(self):
        return self._score

    #获得当前state的最大值
    def get_maxValue(self):
        a = array(self._grid.copy().reshape(16))
        b = array(zeros(16))
        for i in range(len(a)):
            if a[i] == a[i]:
                b[i] = a[i]
            else:
                b[i] = 0
        return b.max()

    # 获得当前state下的未填充数字的个数
    def get_availCellNum(self):
        a = array(self._grid.copy().reshape(16))
        b = array(zeros(16))
        cnt = 0
        for i in range(len(a)):
            if not a[i] == a[i]:
                cnt += 1
        return cnt

    # 判断是不是num，是返回true 若nan返回false
    def isNum(self,a):
        return True if a == a else False

    # 计算当前grid的平滑度
    def get_smoothness(self):
        smoothness_v = 0
        for x in range(4):
            for y in range(4):
                # 如果当前是数字，计算周边的差
                if self.isNum(self._grid[x][y]):
                    value = math.log2(self._grid[x][y])
                    # up  grid[x-1][y] 如果不撞墙
                    if x - 1 > -1 and self.isNum(self._grid[x - 1][y]):
                        smoothness_v -= abs(value - math.log2(self._grid[x - 1][y]))
                    # down  grid[x+1][y] 如果不撞墙
                    if x + 1 < 4 and self.isNum(self._grid[x + 1][y]):
                        smoothness_v -= abs(value - math.log2(self._grid[x + 1][y]))
                    # left grid[x][y-1] 如果不撞墙
                    if y - 1 > -1 and self.isNum(self._grid[x][y - 1]):
                        smoothness_v -= abs(value - math.log2(self._grid[x][y - 1]))
                    # right grid[x][y+1] 如果不撞墙
                    if y + 1 > 4 and self.isNum(self._grid[x][y + 1]):
                        smoothness_v -= abs(value - math.log2(self._grid[x][y + 1]))
        return smoothness_v